<?php

class ss_dao_node extends ss_dao_object{
	protected $primary;
	protected $children='';
	protected $parent;
	public $structure;
	
	public function __construct($primary){
		$this->primary=$primary;
	}
	
	public function api_master(){
		return $this->detail();
		$result=array('children'=>$this->childRef()); //'$ref'=>$this->primary(),
		foreach(array('idstructure','level','slug','name') as $val){
			$result[$val]=$this->{$val}();
		}
		return $result;
		//return array('idstructure'=>$this->idstructure(),'lft'=>$this->lft(),'rgt','slug'=>$this->slug(),'name'=>$this->name(),'level'=>$this->level(),'$ref'=>$this->primary());
	}
	
	public function api_detail(){
		$result=$this->getData();
		$result['children']=$this->childRef();
		return $result;
	}
	
	public function path(){
		//ss_error::log(is_object($this->parent),'Obj '.$this->slug);
		if(is_null($this->parent)) return $this->slug();
		//ss_error::log($this->parent->path());
		return $this->parent->path().$this->slug().'/';
	}
	
	public function active($val=null){
		if(!is_null($val)) $this->active=($val?1:0);
		return ($this->active?true:false);
	}
	public function header_recurse($val=null){
		if(!is_null($val)) $this->header_recurse=($val?1:0);
		return ($this->header_recurse?true:false);
	}
	
	public function childNodes(){
		$result=array();
		//ss_error::log($this->structure->getNodeByID(1));
		//ss_error::log($this->children(),'Children');
		foreach($this->children() as $id){
			$result[$id]=$this->structure->getNodeByID($id);
		}
		return $result;
	}
	
	public function children($children=null){
		//ss_error::log($this,'Children');
		if(is_string($children)){
			if($this->children!=$children && (!in_array('children',$this->modified))) $this->modified[]='children';
			$this->children=$children;
		} elseif(is_object($children)){
			foreach($children as $val) $result[]=$val;
			if($this->children!=($children=implode(',',$result)) && (!in_array('children',$this->modified))) $this->modified[]='children';
			$this->children=$children;
		} elseif(is_array($children)){
			if($this->children!=($children=implode(',',$children)) && (!in_array('children',$this->modified))) $this->modified[]='children';
			$this->children=$children;
		}
		//ss_error::log($this->children.' strlen:'.mb_strlen($this->children).' count:'.count(explode(',',trim($this->children))));
		if(is_null($this->children)||empty($this->children)) return array();
		return explode(',',trim($this->children));
	}
	
	public function childRef(){
		$children=array();
		foreach($this->children() as $child){
			if(!empty($child)) $children[]=array('$ref'=>$child);
		}
		return $children;
	}
	
	public function parent($id=null){
		if(!is_null($id)) $this->parent=$id;
		//ss_error::log($this->parent,'Parent of '.$this->slug);
		return $this->parent;
	}
	
	public function hasChildren(){
		return !empty($this->children);
	}
	
	public function catidlist($list=null){
		if($list) $this->catidlist=implode(',',$list);
		//ss_error::log($this->catidlist,'catidlist');
		//ss_error::log(strlen($this->catidlist)==0,'empty');
		return (strlen($this->catidlist)==0?array():explode(',',$this->catidlist));
	}
}


class ss_dao_structure extends ss_dao_object implements  Iterator, ArrayAccess{
    private $position = 0;
	private $keys;
	protected $structure=array();
	protected $slugIndex=array();
	protected $slugLevelIndex=array();
	protected $levelIndex;
	protected $levelparent=array();
	
    function rewind() {
        //var_dump(__METHOD__);
        $this->position = 0;
    }

    function current() {
        //var_dump(__METHOD__);
        return $this->structure[$this->keys[$this->position]];
    }

    function key() {
        //var_dump(__METHOD__);
        return $this->keys[$this->position];
    }

    function next() {
        //var_dump(__METHOD__);
        ++$this->position;
    }

    function valid() {
        //var_dump(__METHOD__);
        return isset($this->keys[$this->position]);
    }
	
    public function offsetSet($offset, $value) {
        $this->structure[$offset] = $value;
    }
    public function offsetExists($offset) {
        return isset($this->structure[$offset]);
    }
    public function offsetUnset($offset) {
        unset($this->structure[$offset]);
    }
    public function offsetGet($offset) {
        return isset($this->structure[$offset]) ? $this->structure[$offset] : null;
    }
	
	public function append(ss_dao_node $obj){
		//ss_error::log($obj,'Append');
		if(count($this->levelparent)>0){
			/*
			$last=end($this->structure);
			if($last->lft<$obj->lft && $last->rgt>$obj->rgt){
				$obj->parent($last->{$this->primary});
			} elseif($obj->lft>($last->rgt+1)) {
				//$parent=end($this->levelIndex[$obj->level()-1]);
				$obj->parent(end($this->levelIndex[$obj->level()-1])->{$this->primary});
			} else {
				$obj->parent($last->parent());
				//$parent=$this->structure[$last->parent()];
			}
			*/
			$obj->parent($this->levelparent[$obj->level()-1]);
		}
		$this->structure[$obj->{$this->primary}]=$obj;
		$this->slugLevelIndex[($obj->level())][]=$obj->slug;
		$this->slugIndex[$obj->{$this->primary}()]=$obj->slug;
		$this->keys[]=$obj->{$this->primary};
		$this->levelIndex[$obj->level()][]=$this->structure[$obj->{$this->primary}];
		$obj->structure=$this;
		$this->levelparent[$obj->level()]=$obj;
	}
	
	public function getNodeFromSlug($slug){
		return $this->getNodeByID(array_search($slug,$this->slugIndex));
	}
	
	public function getNodeByID($id){
		
		return $this->structure[$id];
	}
	/*
	public function getStructArray(){
		$result=array();
		foreach($this->structure as $id=>$obj){
			$row=array();
			foreach(array('idstructure','parent','children','slug','name') as $attr){
				$row[$attr]=$obj->{$attr}();
			}
			$result[]=$row;
		}
		return $result;
	}
	*/
	public function levelHasSlug($lvl, $slug){
		if(array_key_exists($lvl, $this->slugLevelIndex) && is_array($this->slugLevelIndex[$lvl]) && in_array($slug,$this->slugLevelIndex[$lvl])) return true;
		return false;
	}
	
	public function classForSlug($slug){
		if(($key=array_search($slug,$this->slugIndex))===false) return false;
		return $this->structure[$key]->class_structure();
	}
	
	public function active(){
		return count($this->structure)>0;
	}
	
	public function api_master(){
		$result=array();
		foreach($this->structure as $id=>$obj){
			$row=array();
			foreach(array('idstructure','slug','name') as $attr){
				$row[$attr]=$obj->{$attr}();
			}
			$children=array();
			foreach($obj->children() as $child){
				$children[]=array('$ref'=>$child);
			}
			$row['children']=$children;
			$result[]=$row;
		}
		return $result;
	}
}

class ss_dao_structure_mapper extends ss_dao_mapper{
	protected $primary='idstructure';
	protected $table='structure';
	protected $structure;
	protected $nodeClass='site_dao_node';
	protected $structureClass='site_dao_structure';
	protected $location=array(
		'structure'=>Array(
			'fields'=>array('active','iscategory','timedpub','date_create','date_mod','date_rej','date_pub')
		),
		'structure_item'=>array(
			'fields'=>array('name','slug','headline','header','header_method','header_recurse','instance_date_mod','instance_date_create','itemtype_iditemtype')
		)
	);
	protected $reftables=array(
			'catidlist'=>array('name'=>'category_idcategory','ref'=>'structure_item_idstructure_item','table'=>'structure_item_has_category')
		);
		
	public function __construct(ss_dao_da_interface $da, ss_core_injectorInterface $injector){
		parent::__construct($da, $injector);
		$this->setStructure();
	}
	
	public function setInjector(Phemto $injector){
		$this->injector=$injector;
		$this->structure=$this->injector->fill('primary')->with($this->primary)->create((class_exists($this->structureClass)?$this->structureClass:'ss_dao_structure'));
	}
	
	public function setStructure(){
		//$this->structure=$this->injector->get(class_exists($this->structureClass)?$this->structureClass:'ss_dao_structure');
		$className=(class_exists($this->structureClass)?$this->structureClass:'ss_dao_structure');
		$this->structure=new $className($this->primary);
	}
	
	public function api_master($query=null){
		ss_error::info('Calling structure api_master');
		//$result=$this->injector->fill('primary')->with('idstructure')->create('ss_dao_structure');
		$result=$this->structure;
		if($result->active()) return $result;
		$q=$this->getQueryBuilder();
		
		$q->from(array('api_structure'));
		if($query && $query!='*') $q->where(array($query));
		$sql=$q->select();
		//$stmt=$this->da->prepare($sql);
		//$this->da->execute($stmt,$query->values());
		$stmt=$this->da->runStmt($sql,$q->values());
		while($obj=$stmt->fetchObject((class_exists($this->nodeClass)?$this->nodeClass:'ss_dao_node'),array($this->primary))){
			//ss_error::log($obj);
			$result->append($obj->clean());
		}
		$this->da->closeCursor($stmt);
		return $result;
	}
	
	public function master($query=null){
		//$result=$this->injector->fill('primary')->with('idstructure')->create('ss_dao_structure');
		$result=$this->structure;
		if($result->active()) return $result;
		$q=$this->getQueryBuilder();
		
		$q->from(array('ss_structure'));
		if($query && $query!='*') $q->where(array($query));
		$sql=$q->select();
		//$stmt=$this->da->prepare($sql);
		//$this->da->execute($stmt,$query->values());
		$stmt=$this->da->runStmt($sql,$q->values());
		while($obj=$stmt->fetchObject((class_exists($this->nodeClass)?$this->nodeClass:'ss_dao_node'),array($this->primary))){
			//ss_error::log($obj);
			$result->append($obj->clean());
		}
		$this->da->closeCursor($stmt);
		return $result;
	}
	
	public function api_detail($id){
		$q=$this->getQueryBuilder();
		$sql=$q
		->from(array(
			'api_structure'))
		->where(array(
			array($this->primary=>$id))
			)
		->select();
		$stmt=$this->da->runStmt($sql,$q->values());
		return $stmt->fetchObject('ss_dao_node',array($this->primary));
	}
	
	public function meta($obj){
		$q=$this->getQueryBuilder();
		$sql=$q
		->what(array('key','value'))
		->from(array(
			'structure_meta'))
		->where(array(
			array('structure_item_idstructure_item'=>$obj->idstructure_item()))
			)
		->select();
		$stmt=$this->da->runStmt($sql,$q->values());
		$obj->setMeta($this->da->getMetaArray($stmt));
		return $obj;
		//return $this->da->getMetaArray($stmt);
		//return $stmt->fetchObject('ss_dao_node',array($this->primary));
	}
	
	private function refToChildren($ref){
		$result=array();
		foreach($ref as $obj) $result[]=current($obj);
		return $result;
	}
	
	private function moveNode($node,$pos){
		if(is_object($node)) $node=$node->primary();
		//$sql='CALL mpttMoveNodeAfter(:node,:pos);';
		//$stmt=$this->da->runStmt($sql,array(':node'=>$node,':pos'=>$pos));
		$sql="CALL mpttMoveNodeAfter({$this->table},{$node},{$pos});";
		//$stmt=$this->da->query($sql);
		$stmt=$this->da->exec($sql);
		//$this->da->commit();
	}
	
	public function put($query,$body,$data){
		$recChildren=$body['children']=$this->refToChildren($body['children']);
		$objChildren=$data->children();
		if($recChildren==$objChildren){
		} elseif(count($recChildren)==count($objChildren)){
			if(count($recDiff=array_diff_assoc($recChildren,$objChildren))==2){
				$sid=array_shift($recDiff);
				$pos=$this->structure->getNodeByID(array_pop($recDiff))->lft()-1;
			} else {
				$objDiff=array_diff_assoc($objChildren,$recChildren);
				//ss_error::log($recDiff,'Rec diff');
				//ss_error::log($objDiff,'Obj diff');
				if(current($recDiff)==end($objDiff)){
					$sid=array_shift($recDiff);
					$pos=$this->structure->getNodeByID(array_shift($objDiff))->lft()-1;
				} else {
					$sid=array_pop($recDiff);
					$pos=$this->structure->getNodeByID(array_pop($objDiff))->rgt();
				}
			}
			
		} elseif(count($diff=array_diff($recChildren,$objChildren))>0){
			if(count($objChildren)==0){
				$sid=current($recChildren);
				$pos=$data->lft();
			} else {
				$key=current(array_keys($diff));
				$sid=$diff[$key];
				if($key==0){
					$pos=$this->structure->getNodeByID($recChildren[1])->lft()-1;
				} else {
					$pos=$this->structure->getNodeByID($recChildren[$key-1])->rgt();
				}
			}
		}
		if(isset($sid) && isset($pos)){
			//ss_error::log("Move child {$sid} to below pos {$pos}");
			$this->moveNode($sid,$pos);
		}
		$this->updateRefTable($this->reftables,$body,$data);
		foreach($body as $key=>$val){
			if(!empty($key)) $data->{$key}($val);
			//if($val!=$data->{$key}()) $result[$key]=$val;
		}
		//ss_error::log($data->changed());
		$this->update($data);
	}
	
	public function update(ss_dao_object_interface $obj){
		$result=array();
		$changed=array();
		foreach($obj->changed() as $val) $changed[$val]=$obj->{$val};
		ss_error::log($changed,'Changed data');
		if(count($changed)==0) return false;
		foreach($this->location as $table=>$valid){
			//$result[$table]['data']=array();
			foreach($valid['fields'] as $val){
				//ss_error::log($val,'Checking field');
				if(array_key_exists($val,$changed)) $result[$table]['data'][$val]=$changed[$val];
			}
		}
		ss_error::log($result,'Changed data');
		foreach($result as $table=>$data){
			if(!$obj->{$this->primary}) return $this->insert($obj);
			$q=$this->getQueryBuilder();
			$sql=$q->from(array(
				$table))
			->set(array($data['data']))
			->where(array(
				array('id'.$table=>$obj->{'id'.$table}))
				)
			->update();
			//ss_error::log($sql);
			//ss_error::log($q->values());
			$stmt=$this->da->runStmt($sql,$q->values());
		}
	}
	
	public function updateRefTable($location,$body,$data){
		foreach($location as $field=>$table){
			//ss_error::log($field,'Checking');
			//ss_error::log($data->{$field}(),'Data');
			//ss_error::log($body[$field],'Body');
			if($data->{$field}() != $body[$field]){
				//ss_error::log($field,'Changes in');
				$delCat=array_diff($data->{$field}(),$body[$field]);
				$addCat=array_diff($body[$field],$data->{$field}());
				$del=$add=null;
				if(count($delCat)>0){
					$del=array(
					array(
						'lft'=>$table['ref'],
						'rgt'=>array('com'=>$data->{$table['ref']}())
						),
					array(
						'lft'=>$table['name'],
						'cmp'=>false,
						'rgt'=>array(
							'com'=>'IN('.implode(',',$delCat).')'
							)
						)
					);
				}
				if(count($addCat)>0){
					foreach($addCat as $val) $add[]=array($table['ref']=>$data->{$table['ref']}(),$table['name']=>$val);
				}
				//ss_error::log($body[$field],'Body');
				//ss_error::log($data->{$field}(),'Data');
				//ss_error::log($delCat,'Delcat');
				//ss_error::log($addCat,'Delcat');
				$this->changeRelation($table['table'], $del, $add);
			}
		}
	}
}
?>